﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Schedule.Support.Sql;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using Noris.Schedule.Support.Core;

namespace Noris.Schedule.Support.Green
{
	#region CLASS RepositorySegmentCls (předek segmentu repozitory)
	/// <summary>
	/// Předek tříd, které registrují sady položek jednoho typu (například Třídy, Vztahy, Pořadače, Filtry, ...)
	/// Předek zajišťuje základní funkcionalitu (tvorba instance, vyhledání a řízení načítání dat, invalidace).
	/// </summary>
	/// <typeparam name="K">Klíč repozitory (Int32, String)</typeparam>
	/// <typeparam name="D">Data v repozitory (Data bussines class)</typeparam>
	public abstract class RepositorySegmentCls<K, D>
		where K : IComparable
		where D : RepositorySegmentItem<K>, IRepositorySegment<K>, new()
	{
		#region Konstrukce
		/// <summary>
		/// Konstruktor. Na vstupu očekává maximální počet záznamů, které může držet v paměti.
		/// Pokud by načítal další záznamy, pak nejstarší z paměti uvolní.
		/// </summary>
		/// <param name="maxItemCnt">Nejvyšší počet záznamů. -1 = neomezeně. 0 = žádný !!!</param>
		public RepositorySegmentCls(int maxItemCnt)
		{
			Data = new Dictionary<K, DataItem>();
			_MaxItemCnt = maxItemCnt;
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "Repo of type " + typeof(D).Name + ", ItemCount = " + this.Data.Count.ToString();
		}
		/// <summary>
		/// Zámek na modifikaci seznamu
		/// </summary>
		private object _AddLocker = new object();
		/// <summary>
		/// Seznam s daty. Vlastní položka s daty je zabalena ve třídě _DataItem, jsou přidány property (IsValid).
		/// </summary>
		protected Dictionary<K, DataItem> Data;
		private int _MaxItemCnt;
		#endregion
		#region Vyhledání a načtení dat, po jednom záznamu
		/// <summary>
		/// Vrátí položku pro požadovaný klíč.
		/// Pokud ještě položka není, načte se pomocí abstraktní metody this.GetDataItem() a uloží.
		/// </summary>
        /// <param name="recordKey">Klíč hledaného záznamu</param>
        /// <returns></returns>
		public D this[K recordKey]
		{
			get
			{
				if (!Data.ContainsKey(recordKey))
				{
					lock (_AddLocker)
					{
						if (!Data.ContainsKey(recordKey))
						{
							string select = this.CreateSelectForKey(recordKey);
							D item = this.LoadDataItem(recordKey, select);
							this.AddItem(recordKey, item);
						}
					}
				}
				else if (!this.Data[recordKey].IsValid)
				{
					lock (_AddLocker)
					{
						string select = this.CreateSelectForKey(recordKey);
						D item = this.LoadDataItem(recordKey, select);
						this.UpdateItem(recordKey, item);
					}
				}
				D result = DataItemGetBefore(this.Data[recordKey].Item);
				return result;
			}
		}
		/// <summary>
		/// Zjistí, zda existuje daný záznam.
		/// Pokud existuje v paměti, vrátí true.
		/// Pokud není v paměti, zkusí jej načíst z databáze.
		/// Pokud existuje v databázi, uloží jej do paměti a vrátí true.
		/// Pokud neexistuje v databázi, vrátí false.
		/// Do paměti si ukládá i informaci o neexistujících záznamech.
		/// </summary>
		/// <param name="recordKey">Klíč hledaného záznamu</param>
		/// <returns>true = záznam byl nalezen / false = záznam neexistuje</returns>
		public bool Contains(K recordKey)
		{
			D item = this[recordKey];
			return (item != null);
		}
		/// <summary>
		/// Pokusí se najít / načíst hodnotu (z paměti, z databáze).
		/// Vrací true = hodnota existuje / false = neexistuje.
		/// Do out parametru ukládá nalezenou hodnotu, nebo null.
		/// </summary>
		/// <param name="recordKey">Klíč hledaného záznamu</param>
		/// <param name="item">Out nalezený záznam s daty, nebo null pokud záznam nebyl nalezen</param>
		/// <returns>true = záznam byl nalezen / false = záznam neexistuje</returns>
		public bool TryGetValue(K recordKey, out D item)
		{
			item = this[recordKey];
			return (item != null);
		}
        /// <summary>
        /// Zjistí, zda daný záznam je načten v paměti.
        /// Pokud není záznam uložen v paměti, nenačítá jej z databáze - tím se liší od metody this.Contains(), která do databáze chodí.
        /// </summary>
		/// <param name="recordKey">Klíč hledaného záznamu</param>
		/// <returns>true = záznam byl nalezen / false = záznam neexistuje</returns>
		public bool ContainsInMemory(K recordKey)
        {
            return this.Data.ContainsKey(recordKey);
        }
        /// <summary>
        /// Pokusí se najít / načíst hodnotu výhradně z paměti (nikdy nejde do databáze).
        /// Vrací true = hodnota existuje / false = neexistuje.
        /// Do out parametru ukládá nalezenou hodnotu, nebo null.
        /// </summary>
        /// <param name="recordKey">Klíč hledaného záznamu</param>
        /// <param name="item">Out nalezený záznam s daty, nebo null pokud záznam nebyl nalezen</param>
        /// <returns>true = záznam byl nalezen / false = záznam neexistuje</returns>
        public bool TryGetValueInMemory(K recordKey, out D item)
        {
            DataItem dataItem;
            if (this.Data.TryGetValue(recordKey, out dataItem))
                item = dataItem.Item;
            else
                item = null;
            return (item != null);
        }
		/// <summary>
		/// Zkusí načíst daný záznam z databáze a přímo jej předat ven.
		/// Z databáze načítá i tehdy, pokud by požadovaný záznam existoval v paměti.
		/// Načtený záznam se po načtení neukládá do paměti.
		/// Jde tedy o obcházku celého systému paměťové cache, 
		/// za účelem povinného načtení údaje z aktuální connection (kterážto může být UserConnection).
		/// </summary>
		/// <param name="recordKey">Klíč hledaného záznamu</param>
		/// <param name="item">Out nalezený záznam s daty, nebo null pokud záznam nebyl nalezen</param>
		/// <returns>true = záznam byl nalezen / false = záznam neexistuje</returns>
		public bool TryLoadFromDatabase(K recordKey, out D item)
		{
			string select = this.CreateSelectForKey(recordKey);
			item = this.LoadDataItem(recordKey, select);
			return (item != null);
		}
        /// <summary>
		/// Zkusí načíst daný záznam z databáze a přímo jej předat ven.
		/// Z databáze načítá i tehdy, pokud by požadovaný záznam existoval v paměti.
		/// Načtený záznam se po načtení neukládá do paměti.
		/// Jde tedy o obcházku celého systému paměťové cache, 
		/// za účelem povinného načtení údaje z aktuální connection (kterážto může být UserConnection).
		/// </summary>
		/// <param name="recordKey">Klíč hledaného záznamu</param>
		/// <param name="item">Out nalezený záznam s daty, nebo null pokud záznam nebyl nalezen</param>
		/// <returns>true = záznam byl nalezen / false = záznam neexistuje</returns>
		public bool TryLoadFromDatabase(K recordKey, DataConnector connector, out D item)
		{
			string select = this.CreateSelectForKey(recordKey);
            item = this.LoadDataItem(recordKey, select, connector);
			return (item != null);
		}
        /// <summary>
        /// Načtení záznamu pro daný klíč a select, z databázového spojení ConnectUncommitted.
        /// Volá virtuální metodu LoadDataItem(recordKey, select, DataConnect.ConnectUncommitted).
        /// </summary>
        /// <param name="recordKey">Klíč záznamu, který se má načíst</param>
        /// <param name="select">SQL SELECT, který je připraven pro načtení dat</param>
        /// <returns></returns>
        protected D LoadDataItem(K recordKey, string select)
        {
            return LoadDataItem(recordKey, select, GetRepositoryConnector());
        }
        #endregion
        #region Vyhledání a načtení dat, po skupinách záznamů
        /// <summary>
		/// Metoda najde a vrátí seznam požadovaných hodnot.
		/// Případné donačtení dat z databáze proběhne jedním dotazem, což je výrazný rozdíl oproti sadě jednotlivých dotazů.
		/// Výstupní seznam bude mít shodný počet vět jako má vstupní požadavek (parametr), věty jsou ve shodném pořadí.
		/// Pokud nebude některý prvek nalezen, na jeho místě bude null.
		/// Pokud bude některý klíč v požadavku vícekrát, bude vícekrát i ve výstupu.
		/// </summary>
		/// <param name="recordNumbers">Klíče požadovaných záznamů</param>
		/// <returns></returns>
        public List<D> GetList(IEnumerable<K> recordNumbers)
        {
            return this.GetList(recordNumbers, false);
        }
        /// <summary>
		/// Metoda najde a vrátí seznam požadovaných hodnot.
		/// Případné donačtení dat z databáze proběhne jedním dotazem, což je výrazný rozdíl oproti sadě jednotlivých dotazů.
		/// Výstupní seznam bude mít shodný počet vět jako má vstupní požadavek (parametr), věty jsou ve shodném pořadí.
		/// Pokud nebude některý prvek nalezen, na jeho místě bude null.
		/// Pokud bude některý klíč v požadavku vícekrát, bude vícekrát i ve výstupu.
		/// </summary>
		/// <param name="recordNumbers">Klíče požadovaných záznamů</param>
        /// <param name="skipNull">Požadavek na vynechání null hodnot: true = do výstupu se nedávají hodnoty, pro které neexistují data</param>
        /// <returns></returns>
		public List<D> GetList(IEnumerable<K> recordNumbers, bool skipNull)
		{
			List<KeyValuePair<K, D>> values = this._GetValues(recordNumbers, skipNull);
			List<D> result = new List<D>();
			foreach (KeyValuePair<K, D> value in values)
				result.Add(value.Value);
			return result;
		}
		/// <summary>
		/// Najde a vrátí seznam položek KeyValuePair pro daný seznam požadovaných klíčů hodnot.
		/// Případné donačtení dat z databáze proběhne jedním dotazem, což je výrazný rozdíl oproti sadě jednotlivých dotazů.
		/// Výstupní seznam je ve shodném pořadí jako je vstupní seznam (enumerátor).
		/// Jako Key se v položkách objeví vstupní číslo záznamu, jako Value pak nalezená hodnota. 
		/// Pokud požadovaná hodnota neexistuje v databázi, je uvedeno null.
		/// </summary>
		/// <param name="recordNumbers">Klíče požadovaných záznamů</param>
		/// <returns></returns>
		public List<KeyValuePair<K, D>> GetValues(IEnumerable<K> recordNumbers)
		{
			return this._GetValues(recordNumbers, false);
		}
        /// <summary>
        /// Najde a vrátí seznam položek KeyValuePair pro daný seznam požadovaných klíčů hodnot.
        /// Případné donačtení dat z databáze proběhne jedním dotazem, což je výrazný rozdíl oproti sadě jednotlivých dotazů.
        /// Výstupní seznam je ve shodném pořadí jako je vstupní seznam (enumerátor).
        /// Jako Key se v položkách objeví vstupní číslo záznamu, jako Value pak nalezená hodnota. 
        /// Pokud požadovaná hodnota neexistuje v databázi, je uvedeno null.
        /// </summary>
        /// <param name="recordNumbers">Klíče požadovaných záznamů</param>
        /// <param name="skipNull">Požadavek na vynechání null hodnot: true = do výstupu se nedávají hodnoty, pro které neexistují data</param>
        /// <returns></returns>
        public List<KeyValuePair<K, D>> GetValues(IEnumerable<K> recordNumbers, bool skipNull)
        {
            return this._GetValues(recordNumbers, skipNull);
        }
        /// <summary>
		/// Najde a vrátí seznam položek KeyValuePair pro daný seznam požadovaných klíčů hodnot.
		/// </summary>
		/// <param name="recordNumbers">Klíče požadovaných záznamů</param>
        /// <param name="skipNull">Požadavek na vynechání null hodnot: true = do výstupu se nedávají hodnoty, pro které neexistují data</param>
		/// <returns></returns>
		private List<KeyValuePair<K, D>> _GetValues(IEnumerable<K> recordNumbers, bool skipNull)
		{
			// 1. Vytvořím si seznam vět, které ještě nejsou v paměti:
			Dictionary<K, object> loadDict = new Dictionary<K, object>();
			foreach (K recordKey in recordNumbers)
			{	// Pokud pro daný klíč nemám načtená data, pak si klíč (jedenkrát) vložím do seznamu k donačtení:
				if (!this.Data.ContainsKey(recordKey) && !loadDict.ContainsKey(recordKey))
					loadDict.Add(recordKey, null);
			}

			// 2. Vytvořím si soupis SQL selectů pro hromadné načítání chybějících záznamů:
			if (loadDict.Count > 0)
			{
				List<string> selectList = this.CreateSelectForKeyList(loadDict.Keys);
				List<D> dataList = this.LoadDataItemList(loadDict.Keys, selectList);

				// Přidám seznam načtených položek do interního seznamu s daty, včetně vyvolání odpovídajících háčků:
				this.AddItems(dataList);
			}

			// 3. Sestavím výstupní list s požadovanými položkami a vrátím jej, jdeme standardní cestou:
			List<KeyValuePair<K, D>> result = new List<KeyValuePair<K, D>>();
            foreach (K recordKey in recordNumbers)
            {
                D data = this[recordKey];
                if (!skipNull || (skipNull && data != null))
                    result.Add(new KeyValuePair<K, D>(recordKey, data));
            }
			return result;
		}
        /// <summary>
		/// Metoda načte z databáze a vrátí sadu záznamů repozitory pro sadu klíčů.
		/// Tato metoda je jedna ze dvou, které fyzicky načítají data z SQL pomocí Db_Layer.GetList do seznamu prvků typu D.
		/// (Druhá metoda je LoadDataItem.)
		/// Záznamy neukládá do paměti ale vrací je jako seznam.
		/// Seznam je následně ukládán do paměti metodou this.AddItems(), která je rovněž virtuální.
		/// </summary>
		/// <param name="keyList">Seznam klíčů záznamů, které se mají načíst</param>
		/// <param name="selectList">Seznam SQL SELECTů, které jsou připraveny pro načtení dat</param>
        protected List<D> LoadDataItemList(IEnumerable<K> keyList, List<string> selectList)
		{
            return this.LoadDataItemList(keyList, selectList, GetRepositoryConnector());
        }
		/// <summary>
		/// Vrátí SQL select podle daného vzoru, do něhož je doplněna filtrační fráze pro dané klíče.
		/// </summary>
		/// <param name="sqlSelect">Vzor SQL selectu</param>
		/// <param name="keys">klíče do filtru</param>
		/// <returns></returns>
		protected string CreateSelectForKey(string sqlSelect, string keys)
		{
			if (keys.Contains(","))
				// Ona čárka může být i v textu: " 'Albatros, 12500' " = když klíčem je string, a čárka je uvnitř stringu.
				// Nic špatného se ale nestane, filtr bude mít tvar: "SELECT ... WHERE key in ('Albatros, 12500') ..." :
				//  (ve frázi IN bude jen jeden text)
				return sqlSelect.Replace(TOKEN_RECORD_NUMBER, " in (" + keys + ")");
			else
				return sqlSelect.Replace(TOKEN_RECORD_NUMBER, " = " + keys);
		}
		#endregion
        #region Vyhledání a načtení celého seznamu dat, nikoli v typu segmentu ale pouze klíč a text
        /// <summary>
        /// Vrátí seznam všech záznamů této repozitory.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<RepositoryTextItem<K>> GetAllTexts()
        {
            if (this.AllTexts == null)
                this.AllTexts = this.LoadAllTexts(null);
            return this.AllTexts;
        }
        /// <summary>
        /// Vrátí seznam všech záznamů této repozitory, které vyhovují podmínce.
        /// </summary>
        /// <param name="where"></param>
        /// <returns></returns>
        public IEnumerable<RepositoryTextItem<K>> GetAllTexts(string where)
        {
            return this.LoadAllTexts(where);
        }
        /// <summary>
        /// Potomek může přepsat, pak vrací seznam existujících položek v databázi (select klíč, název from tabulka where ...).
        /// Pokud chce potomek použít implicitní načítání dat (Db_Layer.GetList()), musí načíst klíč do sloupce "item_key" a text do sloupce "text".
        /// </summary>
        /// <param name="where"></param>
        /// <returns></returns>
        protected virtual IEnumerable<RepositoryTextItem<K>> LoadAllTexts(string where)
        {
            return null;
        }
        /// <summary>
        /// Úložiště pro tabulku obsahující texty všech záznamů.
        /// </summary>
        protected IEnumerable<RepositoryTextItem<K>> AllTexts { get; set; }
        #endregion
        #region Invalidace záznamů - zajištění nutnosti je přenačíst před příštím použitím
        /// <summary>
		/// Resetuje celou tuto cache
		/// </summary>
		public virtual void Reset()
		{
			lock (_AddLocker)
			{
				this.Data.Clear();
			}
		}
		/// <summary>
		/// Resetuje jednu danou položku
		/// </summary>
		/// <param name="recordKey">Klíč položky</param>
		public virtual void Reset(K recordKey)
		{
			lock (_AddLocker)
			{
				if (Data.ContainsKey(recordKey))
					Data.Remove(recordKey);
			}
		}
		/// <summary>
		/// Resetuje řadu položek
		/// </summary>
		/// <param name="recordNumbers">Seznam klíčů položek</param>
		public void Reset(IEnumerable<K> recordNumbers)
		{
			if (recordNumbers == null) return;
			if (recordNumbers.ToArray().Length == 0) return;

			lock (_AddLocker)
			{
				foreach (K recordKey in recordNumbers)
				{
					if (Data.ContainsKey(recordKey))
						Data.Remove(recordKey);
				}
			}
		}
		#endregion
		#region Předpis abstraktních a virtuálních metod
		/// <summary>
		/// Tato property vrátí SQL SELECT pro načtení jednoho záznamu s daty.
		/// SQL SELECT musí ve vhodném místě obsahovat token {{RecordNumber}}, na jehož místo jádro dosadí číslo hledaného záznamu.
		/// Kdo nemá rád explicitní texty, určitě rád použije konstantu TOKEN_RECORD_NUMBER.
		/// </summary>
		protected abstract string SqlSelect { get; }
		/// <summary>
		/// Metoda vytvoří SQL SELECT pro načtení jednoho záznamu repozitory
		/// </summary>
		/// <param name="recordKey"></param>
		/// <returns></returns>
		protected virtual string CreateSelectForKey(K recordKey)
		{
			string select = SqlSelect;
			string token = TOKEN_RECORD_NUMBER;
			if (!select.Contains(token))
				Throw.SysError(MessageInfo.Get("V property %0.SqlSelectOne není přítomen token %1.", this.GetType().Name, token));
			select = select.Replace(token, " = " + SqlFormat.ToSql(recordKey));
			return select;
		}
		/// <summary>
		/// Metoda vytvoří sadu SQL SELECTů pro načítání řady záznamů repozitory.
		/// Metoda vytváří SELECTY tak, aby jejich délka (stringu) nepřesáhla 4KB.
		/// </summary>
		/// <param name="recordKey"></param>
		/// <returns></returns>
		protected virtual List<string> CreateSelectForKeyList(IEnumerable<K> recordKeyList)
		{
			List<K> keyList = new List<K>(recordKeyList);
			keyList.Sort();
			// Protože seznam chybějících klíčů může být skutečně dlouhý, zajistím jeho rozdělení do dílčích SQL příkazů, 
			//   vždy pro načítání například 60 záznamů (nebo jak to vyjde).
			// Dělení: tak, aby délka textu celého SQL příkazu (SELECT ... FROM ... WHERE key in (list klíčů)) byla menší než 4 KB:
			string sqlSelect = SqlSelect;
			
			// Nyní připravíme List obsahující množinu klíčů, kde každý prvek bude mít délku menší než danou:
			int maxLength = 4000 - sqlSelect.Length;             // Maximální délka jednoho SQL příkazu
			List<string> keySetList = Library.ListToStrings<K>(keyList, ",", maxLength, true);

			// Nyní připravím List, který bude obsahovat již kompletní SQL selecty: "SELECT ... FROM ... WHERE key IN (keySet)":
			List<string> selectList = new List<string>();
			foreach (string keySet in keySetList)
				selectList.Add(this.CreateSelectForKey(sqlSelect, keySet));

			return selectList;
		}
		/// <summary>
		/// Metoda načte z databáze a vrátí záznam repozitory pro daný klíč.
		/// Tato metoda je jedna ze dvou, které fyzicky načítají data z SQL pomocí Db_Layer.GetList do seznamu prvků typu D.
		/// (Druhá metoda je LoadDataItemList.)
		/// Záznam neukládá do paměti, ale vrací jej.
		/// Záznam je následně ukládán do paměti metodou this.AddItem(), která je rovněž virtuální.
		/// </summary>
		/// <param name="recordKey">Klíč záznamu, který se má načíst</param>
		/// <param name="select">SQL SELECT, který je připraven pro načtení dat</param>
        /// <param name="connector">Konektor na data, používá se pro čtení v metodách Db_Layer.Get???(,, connector).</param>
        /// <returns>Načtený záznam</returns>
        protected virtual D LoadDataItem(K recordKey, string select, DataConnector connector)
		{
            if (connector == null) connector = GetRepositoryConnector();          // Konektor na data: dodaný, anebo ConnectUncommitted
            D item = Db_Layer.GetRecord<D>(select, connector);
            if (connector.IsTypeExternal)
                item.ExternalDataConnector = connector;
            return item;
		}
		/// <summary>
		/// Metoda načte z databáze a vrátí sadu záznamů repozitory pro sadu klíčů.
		/// Tato metoda je jedna ze dvou, které fyzicky načítají data z SQL pomocí Db_Layer.GetList do seznamu prvků typu D.
		/// (Druhá metoda je LoadDataItem.)
		/// Záznamy neukládá do paměti ale vrací je jako seznam.
		/// Seznam je následně ukládán do paměti metodou this.AddItems(), která je rovněž virtuální.
		/// </summary>
		/// <param name="keyList">Seznam klíčů záznamů, které se mají načíst</param>
		/// <param name="selectList">Seznam SQL SELECTů, které jsou připraveny pro načtení dat</param>
        /// <param name="connector">Konektor na data, používá se pro čtení v metodách Db_Layer.Get???(,, connector).</param>
        /// <returns>Seznam s načtenými daty</returns>
        protected virtual List<D> LoadDataItemList(IEnumerable<K> keyList, List<string> selectList, DataConnector connector)
		{
            if (connector == null) connector = GetRepositoryConnector();          // Konektor na data: dodaný, anebo ConnectUncommitted
            List<D> dataList = null;
			if (selectList.Count == 1)
			{	// Pro jediný SELECT:
                dataList = Db_Layer.GetList<D>(selectList[0], connector);
			}
			else
			{	// Klíčů je mnoho, načítáme pomocí více SELECTů:
				dataList = new List<D>();
				foreach (string select in selectList)
                    dataList.AddRange(Db_Layer.GetList<D>(select, connector));
			}

            if (connector.IsTypeExternal)
            {
                foreach (D item in dataList)
                    item.ExternalDataConnector = connector;
            }
			return dataList;
		}
		/// <summary>
		/// Zajistí přidání jedné položky z daného seznamu do interního soupisu dat (Add).
		/// Neřeší zámky (ty jsou připravené).
		/// Zajistí volání háčků.
		/// </summary>
		/// <param name="key">Klíč záznamu (nelze odvodit z prvku item, to může být někdy null)</param>
		/// <param name="item">Data prvku, nebo null když prvek v databázi neexistuje</param>
		protected virtual void AddItem(K key, D item)
		{
			CallRetrieveAfterForItem(item);
			DataItemRetrieveAfter(item);
			DataItemAddBefore(item);
			DataItem dataItem = new RepositorySegmentCls<K, D>.DataItem(item);
			this.Data.Add(key, dataItem);
		}
		/// <summary>
		/// Zajistí aktualizaci jedné položky z daného seznamu do interního soupisu dat (Replace).
		/// Neřeší zámky (ty jsou připravené).
		/// Zajistí volání háčků.
		/// </summary>
		/// <param name="key">Klíč záznamu (nelze odvodit z prvku item, to může být někdy null)</param>
		/// <param name="item">Data prvku, nebo null když prvek v databázi neexistuje</param>
		protected virtual void UpdateItem(K key, D item)
		{
			CallRetrieveAfterForItem(item);
			DataItemRetrieveAfter(item);
			DataItemValidateBefore(item);
			DataItem dataItem = new RepositorySegmentCls<K, D>.DataItem(item);
			this.Data[key] = dataItem;
		}
		/// <summary>
		/// Zajistí přidání položek z daného seznamu do interního soupisu dat.
		/// Zajistí zámky a volání háčků.
		/// </summary>
		/// <param name="dataList"></param>
		protected virtual void AddItems(IEnumerable<D> dataList)
		{
			if (dataList == null) return;
			if (dataList.ToArray().Length == 0) return;
			lock (_AddLocker)
			{	// Nyní do seznamu nikdo nic nevloží, mám jej zamčený:
				foreach (D item in dataList)
				{
					IRepositorySegment<K> iKey = (IRepositorySegment<K>)item;
					K recordKey = iKey.CurrentKey;
					if (!Data.ContainsKey(recordKey))
					{
						CallRetrieveAfterForItem(item);
						DataItemRetrieveAfter(item);
						DataItemAddBefore(item);
						DataItem dataItem = new RepositorySegmentCls<K, D>.DataItem(item);
						this.Data.Add(recordKey, dataItem);
					}
				}
			}
		}
        /// <summary>
        /// Pokud je načtený záznam (v objektu item), pak na něm zavolán háček CallRetrieveAfter().
        /// </summary>
        /// <param name="item"></param>
        protected void CallRetrieveAfterForItem(D item)
        {
            if (item != null)
                item.CallRetrieveAfter();
        }
        /// <summary>
		/// Háček volaný po načtení záznamu z databáze.
		/// Háček se volá i když je přepsaná virtuální metoda this.GetDataItem(), je volán před háčkem DataItemAddBefore() / DataItemValidateBefore()
		/// </summary>
		/// <param name="item"></param>
		protected virtual void DataItemRetrieveAfter(D item)
		{ }
		/// <summary>
		/// Háček volaný před přidáním záznamu do interní kolekce
		/// </summary>
		/// <param name="item"></param>
		protected virtual void DataItemAddBefore(D item)
		{ }
		/// <summary>
		/// Háček volaný před aktualizací záznamu v interní kolekci
		/// </summary>
		/// <param name="item"></param>
		protected virtual void DataItemValidateBefore(D item)
		{ }
		/// <summary>
		/// Háček volaný před vydáním záznamu z paměti do aplikace.
		/// Metoda dostává záznam z paměti, a vrací záznam který se předá aplikaci.
		/// </summary>
		/// <param name="item">Záznam z paměti</param>
		/// <returns>Záznam určený pro aplikaci (může to být vstupní item, nebo jeho klon).</returns>
		protected virtual D DataItemGetBefore(D item)
		{
			return item;
		}
		/// <summary>
		/// Text tokenu {{RecordNumber}}
		/// </summary>
		protected const string TOKEN_RECORD_NUMBER = "{{RecordNumber}}";
        /// <summary>
        /// Vrací connection, která je vhodná pro čtení repository.
        /// Pokud je aktuálně aktivní UserConnection, vrací ji. Jinak vrací ReadUncommitted.
        /// </summary>
        /// <returns></returns>
        internal static DataConnector GetRepositoryConnector()
        {
            if (DataConnect.CurrentConnectType == DataConnectType.UserDefined) return DataConnect.ConnectUserDefined;
            return DataConnect.ConnectUncommitted;
        }
        #endregion
        #region class DataItem : jedna položka uložená v datech. Obsahuje datovou položku plus invalidátor.
        /// <summary>
		/// DataItem : jedna položka uložená v datech. Obsahuje datovou položku plus invalidátor.
		/// </summary>
		protected class DataItem
		{
			internal DataItem(D item)
			{
				this.Item = item;
				this.IsValid = true;
			}
			/// <summary>
			/// Objekt s daty
			/// </summary>
			internal D Item { get; set; }
			/// <summary>
			/// Příznak, že data v tomto objektu jsou platná (true) / neplatná (false)
			/// </summary>
			internal bool IsValid { get; set; }
		}
		#endregion
	}
	#endregion
	#region CLASS RepositorySegmentItem (předek jednoho záznamu v repozitory)
	/// <summary>
	/// Třída, na které jsou postaveny položky jednotlivých segmentů v repozitory (odpovídá jednomu záznamu)
	/// </summary>
	public abstract class RepositorySegmentItem<K> : IRepositorySegment<K>
		where K : IComparable
	{
		#region PROPERTY
		K IRepositorySegment<K>.CurrentKey { get { return this.CurrentKeyValue; } }
		/// <summary>
		/// Klíčová hodnota záznamu. Potomek musí implementovat = namapovat na konkrétní property, která nese skutečný klíč záznamu.
		/// </summary>
		protected abstract K CurrentKeyValue { get; set; }
		#endregion
        #region Podpora pro načítání z explicitního DataConnectoru
        /// <summary>
        /// Externí data connector.
        /// Typicky obsahuje DataConnect.ConnectUncommitted.
        /// Může obsahovat explicitní konektor typu IsTypeExternal (UserDefined nebo Free), pokud s jeho pomocí byla načítána data.
        /// </summary>
        internal DataConnector ExternalDataConnector
        {
            get
            {
                if (this._ExternalDataConnectorWr != null && this._ExternalDataConnectorWr.IsAlive && this._ExternalDataConnectorWr.Target is DataConnector)
                {
                    DataConnector dc = this._ExternalDataConnectorWr.Target as DataConnector;
                    if (dc.IsReady) return dc;
                }
                return GetRepositoryConnector();
            }
            set
            {
                if (value != null && value.IsTypeExternal)
                    this._ExternalDataConnectorWr = new WeakReference(value);
                else
                    this._ExternalDataConnectorWr = null;
            }
        }
        private WeakReference _ExternalDataConnectorWr;
        /// <summary>
        /// Vrací connection, která je vhodná pro čtení repository.
        /// Pokud je aktuálně aktivní UserConnection, vrací ji. Jinak vrací ReadUncommitted.
        /// </summary>
        /// <returns></returns>
        internal static DataConnector GetRepositoryConnector()
        {
            if (DataConnect.CurrentConnectType == DataConnectType.UserDefined) return DataConnect.ConnectUserDefined;
            return DataConnect.ConnectUncommitted;
        }
        #endregion
        #region VIRTUAL METODY
        /// <summary>
		/// Háček, který je zavolán po načtení dat.
		/// </summary>
		protected virtual void RetrieveAfter()
		{ }
		#endregion
		#region INTERNAL PŘESTUPY DO PROTECTED VIRTUAL METOD
		/// <summary>
		/// Vyvolá se protected metoda položky RetrieveAfter()
		/// </summary>
		internal void CallRetrieveAfter()
		{
			this.RetrieveAfter();
		}
		#endregion
	}
	#endregion
    #region CLASS RepositoryTextItem
    /// <summary>
    /// Třída, které obsahuje klíč a název záznamu.
    /// </summary>
    /// <typeparam name="K"></typeparam>
    public class RepositoryTextItem<K>
        where K : IComparable
    {
        public RepositoryTextItem() { }
        public RepositoryTextItem(K key, string text)
        {
            this._item_key = key;
            this._text = text;
        }
        public override string ToString()
        {
            return this.Text;
        }
        private K _item_key;
        private string _text;
        /// <summary>
        /// Klíč záznamu. Pod ním je možno jej dohledat v repozitory.
        /// </summary>
        public K Key { get { return this._item_key; } }
        /// <summary>
        /// Text záznamu (číslo a název, název).
        /// </summary>
        public string Text { get { return this._text; } }
    }
	#endregion
	#region INTERFACE IRepositorySegment<K> : deklaruje přítomnost property CurrentKey (get) takového typu, jakým je generikum
	/// <summary>
	/// IRepositorySegment&lt;K&gt; : deklaruje přítomnost property CurrentKey (get) takového typu, jakým je generikum
	/// </summary>
	/// <typeparam name="K"></typeparam>
	public interface IRepositorySegment<K>
	{
		/// <summary>
		/// Hodnota klíče tohoto záznamu
		/// </summary>
		K CurrentKey { get; }
	}
	#endregion
}
